#!/usr/bin/env python
# coding=utf-8
import sys
import json
import re
import time
import traceback

from pyVim.connect import Disconnect, SmartConnectNoSSL
from pyVim.connect import vim
from log import debug, info, warn, error
from utils import Singleton

# Parameter name map
region_id = "RegionId"
image_id = "ImageId"
datastore_id = "DatastoreId"
zone_id = "ZoneId"
vm_folder_id = "VmFolderName"
instance_name_id = "InstanceName"
instance_type_id = "InstanceType"
status_code = "StatusCode"
message = "Message"
request_id = "RequestId"
vmnic_e1000 = "E1000"
vmnic_vmxnet2 = "VMXNET2"
vmnic_vmxnet3 = "VMXNET3"
successful_status_code = "200"
failure_status_code = "403"

class VmwareVcenterOperationInterface(Singleton):

    INSTANCE_TYPE_DICT = {
        "test.n1": {
            "cpu": 1,
            "memory": 512
        }
    }

    def notify(self):
        pass

    def __init__(self, host, port, user, password):
        self.host = host
        self.port = port
        self.user = user
        self.pwd = password
        # self.server_ins = SmartConnect(host=self.host, port=self.port, user=self.user, pwd=self.pwd)
        self.server_ins = SmartConnectNoSSL(host=self.host, port=self.port, user=self.user, pwd=self.pwd)

    def __del__(self):
        Disconnect(self.server_ins)

    def _get_container_view(self, vimtype):
        res = None
        try:
            content = self.server_ins.RetrieveContent()
            view = content.viewManager.CreateContainerView(content.rootFolder, [vimtype], True)
            res = view
        except Exception as ex:
            debug(traceback.format_exc())
            error("Failed to execute GetContainerView with some exceptions %s." % (ex))
            raise ex

        return res

    def _get_obj(self, vimtype, name):
        obj = None
        container_view = self._get_container_view(vimtype)
        for c in container_view.view:
            if c.name == name:
                obj = c
                break
        container_view.Destroy()
        return obj

    def _get_all_objs(self, vimtype):
        objs = {}
        container_view = self._get_container_view(vimtype)
        for c in container_view.view:
            objs.update({c: c.name})
        return objs

    def _get_vm_by_name(self, name):
        """
        Find a virtual machine by it's name and return it
        """
        return self._get_obj(vim.VirtualMachine, name)

    def _get_resource_pool_by_name(self, name):
        """
        Find a resource pool by it's name and return it
        """
        return self._get_obj(vim.ResourcePool, name)

    def _get_data_center_by_name(self, name):
        """
        Find a data center by it's name and return it
        :param name:data center name
        :return:data center obj
        """
        return self._get_obj(vim.HostSystem, name)

    def _get_vm_folder_by_name(self, name):
        """
        Find a vm folder by it's name and return it
        :param name:vm folder name
        :return:vm folder obj
        """
        return self._get_obj(vim.Folder, name)

    def _get_data_store_by_name(self, name):
        """
        Find a data store by it's name and return it
        :param name:data store name
        :return:data store obj
        """
        return self._get_obj(vim.Datastore, name)

    def _get_data_store_by_default_way(self):
        """
        Find a data store by default way and return it,
        compare each datastores' size and find the
        largest one.
        :return: data store obj
        """
        name = "vsanDatastore_1"
        obj = self._get_data_store_by_name(vim.Datastore, name)
        return obj

    def _get_cluster_by_name(self, name):
        """
        Find a cluster by it's name and return it
        :param name:cluster name
        :return:cluster name obj
        """
        return self._get_obj(vim.ClusterComputeResource, name)

    def _set_instance_type(self, instance_type):
        if instance_type in self.INSTANCE_TYPE_DICT:
            # config = vim.vm.ConfigSpec(name=vm_name, memoryMB=128, numCPUs=1,
            #                            files=vmx_file, guestId='dosGuest',
            #                            version='vmx-07')
            # cpu/ram changes
            cpu = self.INSTANCE_TYPE_DICT[instance_type]["cpu"]
            mem = self.INSTANCE_TYPE_DICT[instance_type]["memory"]
            vmconf = vim.vm.ConfigSpec(numCPUs=cpu, memoryMB=mem)
            # network adapter settings
            # adaptermap = vim.vm.customization.AdapterMapping()
            # adaptermap.adapter = vim.vm.customization.IPSettings(ip=vim.vm.customization.DhcpIpGenerator(),
            #                                                      dnsDomain='domain.local')
            # static ip
            # adaptermap.adapter = vim.vm.customization.IPSettings(ip=vim.vm.customization.FixedIp(address='10.0.1.10'),
            #                                                       subnetMask='255.255.255.0', gateway='10.0.0.1')
            # IP
            # globalip = vim.vm.customization.GlobalIPSettings()
            # for static ip
            # globalip = vim.vm.customization.GlobalIPSettings(dnsServerList=['10.0.1.4', '10.0.1.1'])

            # Hostname settings
            # ident = vim.vm.customization.LinuxPrep(domain='domain.local',
            #                                        hostName=vim.vm.customization.FixedName(name=self.instance_name))

            # Putting all these pieces together in a custom spec
            # customspec = vim.vm.customization.Specification(nicSettingMap=[adaptermap], globalIPSettings=globalip,
            #                                                 identity=ident)
            return vmconf
        else:
            raise Exception("Failed to get instance spec of %s." % (instance_type))

    def _wait_for_task(self, task):
        msg = ''
        res = False
        task_done = False
        while not task_done:
            if task.info.state == 'success':
                msg = task.info.result
                res = True
                task_done = True

            if task.info.state == 'error':
                msg = "there was an error %s" % (task.info.result)
                task_done = True

            time.sleep(3)

        return (res, msg)

    def _clone_vm(self, src_vm, dst_vm_name, dst_vm_conf, dst_path, resource_pool, datastore):
        # Creating relocate spec and clone spec
        relocate_spec = vim.vm.RelocateSpec()
        relocate_spec.datastore = datastore
        relocate_spec.pool = resource_pool
        # cloneSpec = vim.vm.CloneSpec(powerOn=True, template=False, location=relocateSpec, customization=customspec, config=vmconf)
        cloneSpec = vim.vm.CloneSpec(powerOn=True, template=False, location=relocate_spec, customization=None,
                                     config=dst_vm_conf)

        # Creating clone task
        clone_task = src_vm.Clone(name=dst_vm_name, folder=dst_path, spec=cloneSpec)
        return clone_task

    def _get_network_by_name(self, name):
        """
        Finding network by it's name
        :param name: network name
        :return: network object
        """
        return self._get_obj(vim.Network, name)

    def _add_nic_to_vm(self, vm_name, port_group, nic_type):
        vm = self._get_vm_by_name(vm_name)
        if vm:
            network = port_group
            spec = vim.vm.ConfigSpec()
            nic_changes = []
            nic_spec = vim.vm.device.VirtualDeviceSpec()
            nic_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
            if nic_type == vmnic_e1000:
                nic_spec.device = vim.vm.device.VirtualE1000()
            elif nic_type == vmnic_vmxnet2:
                nic_spec.device = vim.vm.device.VirtualVmxnet2()
            elif nic_type == vmnic_vmxnet3:
                nic_spec.device = vim.vm.device.VirtualVmxnet3()
            nic_spec.device.deviceInfo = vim.Description()
            nic_spec.device.deviceInfo.summary = ""
            nic_spec.device.backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo()
            nic_spec.device.backing.useAutoDetect = False
            nic_spec.device.backing.network = self._get_network_by_name(network)
            nic_spec.device.backing.deviceName = network

            nic_spec.device.connectable = vim.vm.device.VirtualDeviceSpec.ConnectInfo()
            nic_spec.device.connectable.startConnected = True
            nic_spec.device.connectable.allowGuestControl = True
            nic_spec.device.connectable.connected = False
            nic_spec.device.connectable.status = 'untried'
            nic_spec.device.wakeOnLanEnabled = True
            nic_spec.device.addressType = 'assigned'

            nic_changes.append(nic_spec)
            spec.deviceChange = nic_changes
            task = vm.ReconfigVM_Task(spec=spec)
            return self._wait_for_task(task)
        else:
            raise("Failed to find virtual machine %s." % (vm_name))

    def CreateInstance(self, **kwargs):
        res = None
        try:
            datacenter_name = kwargs[region_id]
            template_vm_name = kwargs[image_id]
            datastore_name = kwargs[datastore_id] if datastore_id in kwargs else None
            cluster_name = kwargs[zone_id]
            vm_folder_name = kwargs[vm_folder_id] if vm_folder_id in kwargs else None
            # Finding data center
            datacenter = self._get_data_center_by_name(datacenter_name)
            # Get vm folder
            vm_folder = None
            if vm_folder_name:
                vm_folder = self._get_vm_folder_by_name(vm_folder_name)
            else:
                vm_folder = datacenter.vmFolder
            # Finding data store
            datastore = None
            if datastore_name:
                datastore = self._get_data_store_by_name(datastore_name)
            else:
                datastore = self._get_data_store_by_default_way()
            # Finding cluster
            cluster = self._get_cluster_by_name(cluster_name)
            # Finding resource pool
            resource_pool = cluster.resourcePool
            # Finding source VM
            template_vm = self._get_vm_by_name(template_vm_name)
            # Set new instance name
            instance_name = kwargs[instance_name_id]
            # Set new instance type
            vmconf = self._set_instance_type(kwargs[instance_type_id])
            # clone vm
            task = self._clone_vm(template_vm, instance_name, vmconf,
                                  vm_folder, resource_pool, datastore)
            # wait for task
            ret, msg = self._wait_for_task(task)
            res = {}
            if ret:
                res[status_code] = successful_status_code
            else:
                raise(msg)
            res[message] = msg
            res[request_id] = ""
        except Exception as ex:
            debug(traceback.format_exc())
            error("Failed to execute CreateInstance with some exceptions %s." % (ex))
            res = {status_code: failure_status_code, message: str(ex)}
            res[request_id] = ""

        return res

    def DeleteInstance(self, **kwargs):
        res = None
        try:
            vm_name = kwargs[instance_name_id]
            vm = self._get_vm_by_name(vm_name)
            if vm.runtime.powerState == "poweredOn":
                # Power off the virtual machine first.
                task = vm.PowerOffVM_Task()
                ret, msg = self._wait_for_task(task)
                if not ret:
                    raise(msg)
            task = vm.Destroy_Task()
            ret, msg = self._wait_for_task(task)
            if not ret:
                raise(msg)
            res = {}
            res[status_code] = successful_status_code
            res[message] = msg
            res[request_id] = ""
        except Exception as ex:
            debug(traceback.format_exc())
            error("Failed to execute CreateInstance with some exceptions %s." % (ex))
            res = {status_code: failure_status_code, message: str(ex)}
            res[request_id] = ""

        return res

    def RebootInstance(self, **kwargs):
        res = None
        try:
            vm_name = kwargs[instance_name_id]
            vm = self._get_vm_by_name(vm_name)
            task = vm.ResetVM_Task()
            ret, msg = self._wait_for_task(task)
            if not ret:
                raise(msg)
            res = {}
            res[status_code] = successful_status_code
            res[message] = msg
            res[request_id] = ""
        except Exception as ex:
            debug(traceback.format_exc())
            error("Failed to execute RebootInstance with some exceptions %s." % (ex))
            res = {status_code: failure_status_code, message: str(ex)}
            res[request_id] = ""

        return res

    def StartInstance(self, **kwargs):
        res = None
        try:
            vm_name = kwargs[instance_name_id]
            vm = self._get_vm_by_name(vm_name)
            task = vm.PowerOnVM_Task()
            ret, msg = self._wait_for_task(task)
            if not ret:
                raise(msg)
            res = {}
            res[status_code] = successful_status_code
            res[message] = msg
            res[request_id] = ""
        except Exception as ex:
            debug(traceback.format_exc())
            error("Failed to execute StartInstance with some exceptions %s." % (ex))
            res[request_id] = ""

    def StopInstance(self, **kwargs):
        res = None
        try:
            vm_name = kwargs[instance_name_id]
            vm = self._get_vm_by_name(vm_name)
            task = vm.PowerOffVM_Task()
            ret, msg = self._wait_for_task(task)
            if not ret:
                raise(msg)
            res = {}
            res[status_code] = successful_status_code
            res[message] = msg
            res[request_id] = ""
        except Exception as ex:
            debug(traceback.format_exc())
            error("Failed to execute StopInstance with some exceptions %s." % (ex))
            res[request_id] = ""
